#Main MeSH package
#It creates three different hash tables (but two are combined into one) at different times
#It creates a hash table only when required. Hence the first call to lookup_name or lookup_entryterm will be slow, the subsequent calls will be faster

import difflib

from mesh_node import *
from mesh_entry import *
from parse_mesh import *
from parse_dump import *

class mesh():
	"""
	Requires mesh files if the default files are not present
	"""
	def __init__(self, mesh_filename='MESH/ascii/mtrees2007.bin', dump_filename='dump', des_filename='MESH/ascii/d2007.bin'):
		self.mesh_filename = mesh_filename
		self.dump_filename = dump_filename
		self.des_filename = des_filename
		self.invert = None
		self.des_file = open(self.des_filename, 'r')
		self.entries = {}
		self.name_dict = None
		self.name_list = None
		self.generate_entries()
		
	def generate_entries(self):
		"""
		Reads the files and creates a hash table based on the IDs
		"""
		try:
			f = open(self.dump_filename, 'r')
			f.close()
			print 'Found dump file'
			dump = parse_dump(self.dump_filename, self.des_filename)
			dump.parse()
			print 'Finised parsing dump file'
			self.entries = dump.entries
		except IOError:
			print 'Could not find dump file, creating'
			parse = parse_mesh_text(self.mesh_filename)
			parse.create_table()
			parse.put_linenos(self.des_filename)
			parse.dump_table(self.dump_filename)
			self.entries = parse.entries

	def generate_mesh_entry(self, node):
		"""
		Parses the description file and populates the mesh_entry object of the node
		"""
		try:
			self.des_file.seek(int(node.des_pos))
		except IOError:
			print 'Could not seek to', node.des_pos
			self.des_file = open(self.des_filename, 'r')
		if not node.entry:
			#print 'No node entry'
			node.entry = mesh_entry()
			node.entry.parse_record(self.des_file)
	
	def lookup_id(self, id):
		"""
		Gives a mesh node object corresponding to the id given
		"""
		if self.entries == {}:
			print "No entries"
		try:
			node = self.entries[id]
			if not node.entry:
				self.generate_mesh_entry(node)
			return node
		except KeyError:
			return None
	
	def lookup_name(self, name):
		"""
		Gives a list of mesh nodes corresponding to the name given
		"""
		if not self.invert:
			self.invert_dict()
		try:
			nodelist = self.invert[name]
			for node in nodelist:
				if not node.entry:
					self.generate_mesh_entry(node)
			return nodelist
		except KeyError:
			return None

	def lookup_entry_term(self, term):
		"""
		Gives a list of mesh nodes based on the entry given
		"""
		if not self.name_dict:
			self.create_entry_dict()
		try:
			termlist = self.name_dict[term.lower()]
			return termlist
		except KeyError:
			return None
	
	def get_approximate_terms(self, term):
		"""
		Returns a list of terms close to the given term
		Assumes that the correct term is a little different from the term given.
		"""
		if not self.name_dict:
			self.create_entry_dict()
		if not self.name_list:
			self.name_list = self.name_dict.keys()
		print "Searching in", len(self.name_list), "entries"
		return difflib.get_close_matches(term, self.name_list)
		
	def invert_dict(self):
		"""
		Takes the entries with id as the key and returns a new dict with names as the key
		Returns a special dictionary whose value for any key is a list
		"""
		self.invert = mdict()
		for (id, node) in self.entries.items():
			self.invert[node.name] = node
			
	def create_entry_dict(self):
		"""
		Indexes all nodes based on their entry names
		"""
		self.name_dict = mdict()
		for (id, node) in self.entries.items():
			if node.name:
				self.name_dict[node.name.lower()] = node
			self.generate_mesh_entry(node)
			for	name in node.entry.only_clean_entries():
				self.name_dict[name.lower()] = node
						
	def __getitem__(self, key):
		"""
		Tries to guess what you are looking for and looks up either ID or exact name
		"""
		if len(key) == 3 or '.' in key:
			return self.lookup_id(key)
		else:
			return self.lookup_name(key)
			
